import math
import copy

class Calculator(object):
    
    # raid boss at level 93
    RAID_BOSS_ARMOR_VALUE = 24835.0
    CRITICAL_SUPPRESSION_CHANCE = .03
    GLANCE_CHANCE = .24
    GLANCE_DAMAGE_RATE = .75
    BASE_MELEE_MISS_CHANCE = .075
    BASE_SPELL_MISS_CHANCE = .15
    BASE_DODGE_CHANCE = .075
    BASE_PARRY_CHANCE = .075
    DW_ADDITIONAL_MISS_CHANCE = .19
    OH_DAMAGE_PENATY_RATE = .5
    
    WEAKENED_ARMOR_DEBUFF_PENCENTAGE = .12
    
    # Sharttering Throw
    SHARTTERING_THROW_LAST_TIME = 10.0
    SHARTTERING_THROW_CD = 300.0
    SHARTTERING_ARMOR_REDUCTION = .2
    # Skull Banner
    SKULL_BANNER_LAST_TIME = 10.0
    SKULL_BANNER_CD = 180.0
    SKULL_BANNER_CRIT_DAMAGE_BONUS = .2
    
    def __init__(self, settings, stats, buffs, pet):
        self.settings = settings
        self.stats = stats
        self.base_stats = copy.copy(stats)
        self.buffs = buffs
        self.pet = pet
    
    
    def get_ep(self):
        dps_base = self.get_dps()
        ep = {}
        for stat in self.EP_STATS:
            attr = self.base_stats.__getattribute__(stat) 
            self.base_stats.__setattr__(stat, (attr +  1))
            dps_new = self.get_dps()
            ep[stat] = abs(dps_new - dps_base)
            self.base_stats.__setattr__(stat, attr)
            
        return ep
        
        
    def hit_chance_melee(self, base_miss_chance, weapon_type, dodgeable, parryable):
        
        # get hit rate from equipment and racial
        hit_rate = self.stats.get_hit_from_rating()
        # TODO
        # hit_rate += self.racial.get_hit_from_racial(weapon_type)
        miss_chance = max(base_miss_chance - hit_rate, 0)
        
        # get expertise rate from equipment and racial       
        expertise_rate = self.stats.get_exp_rate_total(weapon_type)
        # TODO
        # expertise_rate += self.racial.get_exp_from_racial(weapon_type)
        
        dodge_chance = 0
        parry_chance = 0
        
        if dodgeable:
            dodge_chance = max(self.BASE_DODGE_CHANCE - expertise_rate, 0)
        
        if parryable:
            parry_chance = max(self.BASE_PARRY_CHANCE - (expertise_rate - self.BASE_DODGE_CHANCE), 0)
        
        hit_chance = 1 - miss_chance - dodge_chance - parry_chance
        return hit_chance
    
    
    def hit_chance_melee_special(self, hand, dodgeable, parryable):
        miss_chance = self.BASE_MELEE_MISS_CHANCE
        # get weapon type
        if hand == 'MH':
            weapon_type = self.stats.mh.weapon_type
        elif hand == 'OH':
            weapon_type = self.stats.oh.weapon_type
        hit_chance = self.hit_chance_melee(miss_chance, weapon_type, dodgeable, parryable)
        
        return hit_chance
        
    # chance with glance   
    def land_chance_melee_dw(self, hand, dodgeable, parryable):
        miss_chance = self.BASE_MELEE_MISS_CHANCE + self.DW_ADDITIONAL_MISS_CHANCE
        # get weapon type
        if hand == 'MH':
            weapon_type = self.stats.mh.weapon_type
        elif hand == 'OH':
            weapon_type = self.stats.oh.weapon_type
        land_chance = self.hit_chance_melee(miss_chance, weapon_type, dodgeable, parryable)
        
        return land_chance
    
    # chance without glance
    def hit_chance_melee_dw(self, hand, dodgeable, parryable):
        return self.melee_land_chance_dw(hand, dodgeable, parryable) - self.GLANCE_CHANCE
    
        
    def hit_chance_spell(self):        
        # get hit rate from equipment and racial
        hit_rate = self.stats.get_hit_from_rating()
        # get expertise rate from equipment and racial       
        expertise_rate = self.stats.get_exp_rate_total()
        miss_chance = max(self.BASE_SPELL_MISS_CHANCE - hit_rate - expertise_rate, 0)
        return 1 - miss_chance
    
    def base_crit_chance(self):
        # get critical chance from equipment, buffs, racial
        crit_rate = self.stats.get_crit_from_rating()
        crit_rate += self.buffs.get_crit_buff_modifier()
        # TODO
        
        crit_chance = max(crit_rate - self.CRITICAL_SUPPRESSION_CHANCE, 0)
        return crit_chance
    
    def weapon_damage(self, ap, hand):
        damage = 1
        return damage
    
    def mainhand_damage(self, ap):
        damage = self.weapon_damage(ap, 'MH')
        return damage
        
    def offhand_damage(self, ap):
        damage = self.weapon_damage(ap, 'OH')
        return damage
    
    # check weakened armor debuff
    # Override this for monk  
    def target_armor(self, throw):
        target_armor = self.RAID_BOSS_ARMOR_VALUE
        if self.buffs.weaken_armor:
            target_armor *= (1 - self.WEAKENED_ARMOR_DEBUFF_PENCENTAGE)
        # Sharterring Throw
        if throw:
            target_armor *= (1 - self.SHARTTERING_ARMOR_REDUCTION)
        return target_armor
            
    def damage_modifier_by_armor(self): 
        target_armor_normal = self.target_armor(throw=False)
        target_armor_throw = self.target_armor(throw=True)
        # DamageReduction = TargetArmor / (TargetArmor + 4037.5*AttackerLevel - 317117.5) (source: Blz)
        damage_reduction_normal = target_armor_normal / (target_armor_normal + 4037.5 * 90 - 317117.5)
        damage_reduction_throw = target_armor_throw / (target_armor_throw + 4037.5 * 90 - 317117.5)
        
        throw_uptime = self.get_spell_uptime(self.buffs.sharttering_throw*self.SHARTTERING_THROW_LAST_TIME, self.SHARTTERING_THROW_CD)
        damage_reduction = damage_reduction_normal * (1 - throw_uptime) +  damage_reduction_throw * throw_uptime
        return 1 - damage_reduction
    
    
    def crit_damage_rate(self):
        damage_rate = self.CRITICAL_DAMAGE_RATE_BASE
        # check gem bonus
        damage_rate *= self.stats.get_meta_crit_damage_multiplier()
        # Skull Banner
        uptime = self.get_spell_uptime(self.buffs.crit_damage*self.SKULL_BANNER_LAST_TIME, self.SKULL_BANNER_CD)
        damage_rate = 1 + (damage_rate - 1) * (1 + self.SKULL_BANNER_CRIT_DAMAGE_BONUS * uptime)
        
        return damage_rate
    
    
    def is_two_hand_weapon(self):
        if self.stats.mh.weapon_type in ('2H_POLEARM', '2H_STAFF'):
            return True
        else:
            return False
        
    def is_dual_wield(self):
        return not self.is_two_hand_weapon()
    
    
    def get_spell_uptime(self, last, cd):
        count_full = math.floor(self.settings.combat_time / cd)
        time_full = count_full * last
        time_left = self.settings.combat_time - count_full * cd
        time_total = time_full +  min(time_left, last)
        
        return time_total / self.settings.combat_time
    
    
    def update_base_stats_by_buffs(self):
        # Flask
        if self.buffs.flask == 'AGI':
            self.stats.agi += self.buffs.FLASK_VALUE_AGILITY
            
        # Food
        if self.buffs.food == 'AGI':
            self.stats.agi += self.buffs.FOOD_VALUE_AGILITY * self.stats.race.get_food_value_multiplier_racial()
        
        # Potion
        if self.buffs.potion == 'AGI':
            count = 1
            if self.buffs.pre_pot:
                count += 1
            uptime = count * self.buffs.POTION_LAST_TIME_AGILITY / self.settings.combat_time
            self.stats.agi += self.buffs.POTION_VALUE_AGILITY * uptime
        
        # 5% stats
        self.stats.str *= self.buffs.get_stats_buff_multiplier()
        self.stats.agi *= self.buffs.get_stats_buff_multiplier()
        self.stats.int *= self.buffs.get_stats_buff_multiplier()
        
        self.stats.agi *= self.stats.get_armor_specializationm_multiplier()
        
        self.stats.ap = self.stats.str + self.stats.agi * 2
        self.stats.ap *= self.buffs.get_ap_buff_multiplier()
